iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0

就如前面說的,雲其實包含的東西有點多,這裡會盡量把裡面的算式拆解然後一個一個示範出來,而今天要來看的是 Noise 跟 FSM 的部分。

這邊為了展示 FSM,用了此網站的範例為圖例。

這邊以 Sine Wave 來看就會看到,一般的波沒任何 Noise 的話,就會是以規律的幅度上下幅動。

float amplitude = 1.; //振幅
float frequency = 1.;
y = sin(x * frequency);
// t 時間偏移 
// u_time 程式開始執行後累積的時間
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
//y += sin(x*frequency*1.72 + t*1.121)*4.0;
//y += sin(x*frequency*2.221 + t*0.437)*5.0;
//y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;

如果把後面幾個開始註解掉的話,就會開始看見波的形狀越來越不規律

float amplitude = 1.;
float frequency = 1.;
y = sin(x * frequency);
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
y += sin(x*frequency*1.72 + t*1.121)*4.0;
//y += sin(x*frequency*2.221 + t*0.437)*5.0;
//y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;

float amplitude = 1.;
float frequency = 1.;
y = sin(x * frequency);
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
y += sin(x*frequency*1.72 + t*1.121)*4.0;
y += sin(x*frequency*2.221 + t*0.437)*5.0;
//y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;

float amplitude = 1.;
float frequency = 1.;
y = sin(x * frequency);
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
y += sin(x*frequency*1.72 + t*1.121)*4.0;
y += sin(x*frequency*2.221 + t*0.437)*5.0;
y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;

而這個多個不同 Noise 疊加的結果就是 Fractal Brownian Motion(FBM)。

實作

//fbm.hlsl

cbuffer ShaderConstants : register(b0)
{
    // 螢幕解析度
    float2 u_resolution;
};

// 根據輸入座標產生 0 到 1 之間的偽隨機值
float Random(float2 st)
{
    // 將座標轉換成雜湊結果並只保留小數部分
    return frac(sin(dot(st, float2(12.989f, 78.233f)) * 43758.5453123f));
}

// 根據二維座標計算 Value Noise
float Noise(float2 st)
{
    // 取得目前所在的 grid 座標
    float2 i = floor(st);

    // 取得目前位置在 grid 內的局部座標
    float2 f = frac(st);

    // 取得左下角的隨機值
    float a = Random(i);

    // 取得右下角的隨機值
    float b = Random(i + float2(1.0f, 0.0f));

    // 取得左上角的隨機值
    float c = Random(i + float2(0.0f, 1.0f));

    // 取得右上角的隨機值
    float d = Random(i + float2(1.0f, 1.0f));

    // 使用 Smoothstep 曲線平滑局部座標
    float2 u = f * f * (3.0f - 2.0f * f);

    // 對四個角落的隨機值進行雙線性插值
    return lerp(a, b, u.x) + (c - a) * u.y * (1.0f - u.x)
    + (d - b) * u.x * u.y;
}

#define OCTAVES 6

// 疊加多層不同頻率與振幅的 Noise 產生 Fractal Brownian Motion
float Fbm(float2 st)
{
    // 儲存累積的 Noise 值
    float value = 0.0f;

    // 設定第一層 Noise 的振幅
    float amplitude = 0.5f;

    // 要求 Compiler 展開迴圈以減少執行時的迴圈控制成本
    [unroll]
    for (int i = 0; i < OCTAVES; ++i)
    {
        // 將目前頻率的 Noise 乘上振幅後累加
        value += amplitude * Noise(st);

        // 將座標放大兩倍以提高下一層 Noise 的頻率
        st *= 2.0f;

        // 將下一層 Noise 的振幅降低一半
        amplitude *= 0.5f;
    }

    // 回傳所有 octave 疊加後的結果
    return value;
}

// 使用 Vertex ID 產生 Fullscreen Triangle 的頂點位置
float4 VSMain(uint vertexId : SV_VertexID) : SV_Position
{
    // 根據 Vertex ID 產生三角形頂點座標
    float2 position = float2((vertexId << 1) & 2, vertexId & 2);

    // 將座標轉換到 NDC 範圍並翻轉 Y 軸
    return float4(position * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
}

// 根據 Pixel 的螢幕座標計算最終顏色
float4 PSMain(float4 position : SV_Position) : SV_Target
{
    // 將 Pixel 座標轉換成 0 到 1 的 UV 座標
    float2 st = position.xy / u_resolution.xy;

    // 根據螢幕 Aspect Ratio 修正 X 軸比例避免 Noise 被拉伸
    st.x *= u_resolution.x / u_resolution.y;

    // 放大取樣座標並計算 FBM Noise
    float value = Fbm(st * 3.0f);

    // 將 Noise 值輸出成灰階顏色
    return float4(value, value, value, 1.0f);
}
#include <cstdlib>
#include <directx/d3dx12_core.h>
#include <exception>
#include <stdexcept>

#include "constant_buffer.h"
#include "graphics_engine.h"
#include "my_engine.h"
#include "skyline_debugger.h"
#include "system.h"

namespace
{
struct ShaderConstants
{
    float resolution[2];
};

D3D12_GRAPHICS_PIPELINE_STATE_DESC createPipelineDescription(
    ID3D12RootSignature* rootSignature, ID3DBlob* vertexShader, ID3DBlob* pixelShader)
{
    if (rootSignature == nullptr)
        throw std::invalid_argument("DayX_Fbm: Root signature is required.");
    if (vertexShader == nullptr)
        throw std::invalid_argument("DayX_Fbm: Vertex shader is required.");
    if (pixelShader == nullptr)
        throw std::invalid_argument("DayX_Fbm: Pixel shader is required.");

    D3D12_GRAPHICS_PIPELINE_STATE_DESC description{};
    description.InputLayout = {nullptr, 0};
    description.pRootSignature = rootSignature;
    description.VS = CD3DX12_SHADER_BYTECODE(vertexShader);
    description.PS = CD3DX12_SHADER_BYTECODE(pixelShader);
    description.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
    description.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
    description.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
    description.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
    description.DepthStencilState.DepthEnable = FALSE;
    description.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
    description.DepthStencilState.StencilEnable = FALSE;
    description.SampleMask = UINT_MAX;
    description.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
    description.NumRenderTargets = 1;
    description.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
    description.DSVFormat = DXGI_FORMAT_D32_FLOAT;
    description.SampleDesc.Count = 1;
    return description;
}
}

int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous, LPWSTR commandLine, int showCommand)
{
    try
    {
        SkylineDebugger::Initialize("DayX_Fbm");
        initWindow(instance, previous, commandLine, showCommand, TEXT("DayX_Fbm"));
        if (g_hWnd == nullptr)
            throw std::runtime_error("DayX_Fbm: Failed to create the application window.");

        GraphicsEngine graphicsEngine;
        graphicsEngine.init(g_hWnd, FRAME_BUFFER_W, FRAME_BUFFER_H);
        SkylineDebugger::ConfigureD3D12(graphicsEngine.getD3DDevice());

        RootSignature rootSignature;
        rootSignature.init(D3D12_FILTER_MIN_MAG_MIP_LINEAR,
                           D3D12_TEXTURE_ADDRESS_MODE_WRAP,
                           D3D12_TEXTURE_ADDRESS_MODE_WRAP,
                           D3D12_TEXTURE_ADDRESS_MODE_WRAP);

        Shader vertexShader, pixelShader;
        vertexShader.loadVS("assets/shaders/fbm.hlsl", "VSMain");
        pixelShader.loadPS("assets/shaders/fbm.hlsl", "PSMain");

        PipelineState pipelineState;
        pipelineState.init(createPipelineDescription(
            rootSignature.get(), vertexShader.getCompiledBlob(), pixelShader.getCompiledBlob()));

        ShaderConstants constants{{static_cast<float>(FRAME_BUFFER_W), static_cast<float>(FRAME_BUFFER_H)}};
        ConstantBuffer constantBuffer;
        constantBuffer.init(sizeof(constants), &constants);

        RenderContext& renderContext = graphicsEngine.getRenderContext();
        while (dispatchWindowMessage())
        {
            graphicsEngine.beginRender();
            renderContext.setRootSignature(rootSignature);
            renderContext.setPipelineState(pipelineState);
            renderContext.setPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
            renderContext.setGraphicsRootConstantBufferView(0, constantBuffer.getGPUVirtualAddress());
            renderContext.draw(3);
            graphicsEngine.endRender();
        }

        SkylineDebugger::Shutdown();
        return EXIT_SUCCESS;
    }
    catch (const std::exception& error)
    {
        SkylineDebugger::ShowFatalError("DayX_Fbm initialization failed", error);
        SkylineDebugger::Shutdown();
        return EXIT_FAILURE;
    }
    catch (...)
    {
        SkylineDebugger::ShowFatalError("DayX_Fbm initialization failed", "An unknown fatal error occurred.");
        SkylineDebugger::Shutdown();
        return EXIT_FAILURE;
    }
}

結果

參考資料

https://thebookofshaders.com/13/
https://www.shadertoy.com/view/4dS3Wd


上一篇
Day 20:Compute Shader、RWStructuredBuffer
下一篇
Day 22:放入 fbx
系列文
因為 AI 看不懂老舊程式,只好乖乖從零開始學 DirectX 12 與 HLSL23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言